package com.vcg.community.service;

import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.apache.log4j.Logger;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import test.TestUser;
import vc.bean.SearchHits;
import vc.dubbo.result.DubboResult;
import vc.search.service.ResourceService;
import vc.search.service.SearchService;

import com.alibaba.dubbo.rpc.RpcContext;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.vcg.community.consumer.SearchConsumer;
import com.vcg.community.dao.ExifMapper;
import com.vcg.community.dao.ResourceMapper;
import com.vcg.community.dao.ResourcetagMapper;
import com.vcg.community.dao.TagMapper;
import com.vcg.community.dao.UserMapper;
import com.vcg.community.dao.UserresourceMapper;
import com.vcg.community.model.Exif;
import com.vcg.community.model.Ratelog;
import com.vcg.community.model.Resource;
import com.vcg.community.model.Resourcetag;
import com.vcg.community.model.Tag;
import com.vcg.community.model.User;
import com.vcg.community.model.Userresource;

public class UploadServiceImpl implements UploadService {

	@Autowired
	private ResourceMapper rsDao=null;
	@Autowired
	private UserMapper userDao=null;
	@Autowired
	private TagMapper tgDao=null;
	@Autowired
	private ResourcetagMapper rstgDao=null;
	@Autowired
	private UserresourceMapper usrResourceDao=null;
	@Autowired
	private ExifMapper exifDao=null;

	//For reading from redis
	@Autowired
	StringRedisTemplate template;
//	@Autowired
	SearchService searchService;
	@Autowired
	ResourceService resourceService;
	
	//For transaction
	@Autowired
	DataSourceTransactionManager txManager;
	
	
	
	//Private property
	Logger logger=Logger.getLogger(UploadServiceImpl.class);
	
	List<String> KeysToRollback=new ArrayList<String>();
	List<String> valuesToRollback=new ArrayList<String>();
	DubboResult dbResult=new DubboResult(200,"",null);

	public DubboResult doUpload(Resource rs, Tag[] tags) {
		// TODO Auto-generated method stub
        System.out.println("[" + new SimpleDateFormat("HH:mm:ss")+"] doUpdate service called.");
        //Transaction start
		DefaultTransactionDefinition def = new DefaultTransactionDefinition();
		def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

		TransactionStatus status = txManager.getTransaction(def);
        //Insert resource to db
		Date insertDate=new Date();
		rs.setCreateddate(insertDate);  //Adding for search
		int iResId =rsDao.insert(rs);
	    logger.info("record inserted:"+rs.toString());
	    if(tags==null){
	    	txManager.commit(status);
	    	return dbResult;
	    }

		for(Tag tag:tags){
			try {
				//Insert resource to redis with key like "resource:id"
				boolean isInsertedToRedis=putRSToRedis(rs);
				if(isInsertedToRedis==false){
					//TODO should rollback
				}
				//Call search service in dubbo
				/*
				SearchConsumer sConsumer=new SearchConsumer();
				DubboResult<String> searchDBResult=sConsumer.doSearchIndex(rs);
				if(searchDBResult==null){
					throw new Exception();
				}
				if(searchDBResult.getCode()!=0){
					dbResult.setCode(searchDBResult.getCode());
					dbResult.setCodeError(searchDBResult.getCodeError());
					throw new Exception();
				}
				*/

				String jsonRSString = JSON.toJSONString(rs);
		        JSONObject jsonRSObject = JSON.parseObject(jsonRSString); 
		        //Change date to string for search
		        DateFormat format1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		        jsonRSObject.remove("createddate");
		        jsonRSObject.remove("id");
		        jsonRSObject.remove("resourceId");
		        jsonRSObject.put("created_date",format1.format(insertDate) );
		        jsonRSObject.put("user",jsonRSObject.get("uploaderName") );
		        jsonRSObject.remove("uploaderName");
//				searchService.index("index", "image",jsonRSObject);
		        DubboResult<String> searchDBResult=resourceService.index(rs.getResourceId(), jsonRSObject);
				if(searchDBResult.getCode()!=200){
					dbResult.setCode(searchDBResult.getCode());
					dbResult.setCodeError(searchDBResult.getCodeError());
					throw new Exception();
				}
				//Update resource-tag in redis and db
				updatePersistence(rs, tag);
			} catch (Exception ex) {
				  txManager.rollback(status);
				  rollbackRedis();
				  logger.error(ex);
				  if(dbResult.getCode()==0){
					  dbResult.setCode(501);
					  dbResult.setCodeError("Error in inserting into DB");
				  }
				  return dbResult;
			}			
		}
		txManager.commit(status);		
		//Transaction End
		return dbResult;

	}
	/*First query if there is tag id in redis 
	  If yes (it exists in both redis and db) do nothing
	  else insert tag id into redis and db
	  insert one tag-resource id pair to redis, db and search
	*/
	private boolean updatePersistence(Resource rs, Tag tg) throws JsonParseException, JsonMappingException, IOException{
	    logger.debug("put RS and Tag to DB");
		//In redis, key is like "tag:test27"
        String strTagNameCn=tg.getNameCn();
        if(strTagNameCn==null){
        	return true;
        }
		StringBuffer cKey = new StringBuffer("tag:");
		cKey.append(tg.getNameCn());
	    String cValue = (String)template.opsForHash().get("hashItem", cKey.toString());
	    //if not in redis then insert into DB, update redis and update cValue
		if(cValue==null){
			int iTagId=tgDao.insert(tg);			
			cValue=tg.toString();
			template.opsForHash().put("hashItem", cKey.toString(),
					cValue);
			//Add key to rollback list
			KeysToRollback.add(cKey.toString());
			valuesToRollback.add("all");
		}
		
		//Recover tagObject from cValue
        ObjectMapper objectMapper = new ObjectMapper();
        Tag tgValue=objectMapper.readValue(cValue, Tag.class);
        //Insert Resourcetag table
		Resourcetag rsTag=new Resourcetag();
		rsTag.setResourceId(rs.getId());
		rsTag.setTagId(tgValue.getId());
		rstgDao.insert(rsTag);
	    logger.info("tag inserted:"+rsTag.toString());
		//Update "resource:resource_id:tag" in Redis
		StringBuffer cKeyResourceTag = new StringBuffer("resource:");
		cKeyResourceTag.append(rs.getResourceId());
		cKeyResourceTag.append(":tag");
		String cResourceTagValue = (String)template.opsForHash().get("hashItem", cKeyResourceTag.toString());
		if(cResourceTagValue==null){
			template.opsForHash().put("hashItem", cKeyResourceTag.toString(),
				strTagNameCn);
			KeysToRollback.add(cKeyResourceTag.toString());
			valuesToRollback.add("all");
		}else{
			template.opsForHash().put("hashItem", cKeyResourceTag.toString(),
					cResourceTagValue+","+strTagNameCn);			
			KeysToRollback.add(cKeyResourceTag.toString());
			valuesToRollback.add(","+strTagNameCn);
		}
		//Add key to rollback list
		KeysToRollback.add(cKeyResourceTag.toString());
		valuesToRollback.add("all");
		//Update "tag:NameCn:resource" in Redis
		StringBuffer cKeyTagResource = new StringBuffer("tag:");
		cKeyTagResource.append(strTagNameCn);
		cKeyTagResource.append(":resource");
	    String cTagResourceValue = (String)template.opsForHash().get("hashItem", cKeyTagResource.toString());
	    if(cTagResourceValue==null){
			template.opsForHash().put("hashItem", cKeyTagResource.toString(),
					""+rs.getResourceId());    	
			//Add key to rollback list
			KeysToRollback.add(cKeyTagResource.toString());
			valuesToRollback.add("all");
	    }else{
			template.opsForHash().put("hashItem", cKeyTagResource.toString(),
					cTagResourceValue+","+rs.getResourceId());    	    	
			valuesToRollback.add(","+rs.getResourceId());
	    }	
		return true;
	}
	private boolean putRSToRedis(Resource cRes){
	    logger.debug("put RS to redis:"+cRes.toString());
	    //resource:resource_id
 	StringBuffer cKey = new StringBuffer("resource:");
    	cKey.append(cRes.getResourceId().toString());

	    String cValue = (String)template.opsForHash().get("hashItem", cKey.toString());
	    //if not in redis then insert into DB, update redis and update cValue
		if(cValue==null){
	    	template.opsForHash().put("hashItem", cKey.toString(), cRes.toString());
			//Add key to rollback list
			KeysToRollback.add(cKey.toString());
			valuesToRollback.add("all");
		}
		return true;
	}
	public DubboResult doUpload(Integer userId, Resource rs, Tag[] tags) {
		/* To Do
		 * Add user resource
		 */
		DubboResult resultObject= doUpload(rs,tags);
		if(resultObject.getCode()!=0){
			return resultObject;
		}
		
		DefaultTransactionDefinition def = new DefaultTransactionDefinition();
		def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

		TransactionStatus status = txManager.getTransaction(def);

		try{
			Userresource urObject=new Userresource();
			urObject.setUserId(userId);
			urObject.setResouceId(rs.getId());;
			usrResourceDao.insert(urObject);
		}catch(Exception ex){
			txManager.rollback(status);
			resultObject.setCode(501);
			resultObject.setCodeError(ex.toString());
			return resultObject;
		}
		txManager.commit(status);
		return resultObject;		
	}
	public DubboResult doUpload(Integer userId, Resource rs, Tag[] tags,Exif exif){
		DubboResult resultObject= doUpload(userId,rs,tags);
		if(resultObject.getCode()!=200){
			return resultObject;
		}
		
		DefaultTransactionDefinition def = new DefaultTransactionDefinition();
		def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

		TransactionStatus status = txManager.getTransaction(def);

		try{
			exif.setResourceId(rs.getId());
			exifDao.insert(exif);
		}catch(Exception ex){
			txManager.rollback(status);
			resultObject.setCode(501);
			resultObject.setCodeError(ex.toString());
			return resultObject;
		}
		txManager.commit(status);
		return resultObject;		
		
	}
	
	public DubboResult doUpload(String strUserId, Resource rs, Tag[] tags,Exif exif){
		User usr=userDao.SelectUserByStringId(strUserId);
		return doUpload(usr.getId(),rs,tags,exif);
	}
	
	private void rollbackRedis(){
		for(int i=0;i< KeysToRollback.size();i++ ){
			String strToDel=valuesToRollback.get(i);
			if("all".equals(strToDel)){
				template.opsForHash().delete("hashItem", KeysToRollback.get(i));
			}else{
				if(strToDel!=null){
					String strKey=KeysToRollback.get(i);
					String strValue=(String) template.opsForHash().get("hashItem",strKey );
					strValue=strValue.replaceFirst(strToDel, "");
					template.opsForHash().put("hashItem",strKey, strValue);
				}
			}
		}	
	}

}
